/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.rultax.neural.feedforward;

import com.rultax.exception.NeuralNetworkError;
import com.rultax.matrix.MatrixCODEC;
import com.rultax.neural.util.ErrorCalculation;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

/**
 *
 * @author Scott
 */
public class FeedForwardNetwork implements Serializable {

    protected FeedForwardLayer inputLayer;
    protected FeedForwardLayer outputLayer;
    protected List<FeedForwardLayer> layers = new ArrayList<FeedForwardLayer>();

    public FeedForwardNetwork() {
    }

    public void addLayer(final FeedForwardLayer layer) {
        if (this.outputLayer != null) {
            layer.setPrevious(this.outputLayer);
            this.outputLayer.setNext(layer);
        }

        if (this.layers.isEmpty()) {
            this.inputLayer = this.outputLayer = layer;
        } else {
            this.outputLayer = layer;
        }

        this.layers.add(layer);
    }

    public double calculateError(final double[][] input, final double[][] ideal)
            throws NeuralNetworkError {
        final ErrorCalculation errorCalculation = new ErrorCalculation();

        for (int i = 0; i < ideal.length; i++) {
            computeOutputs(input[i]);
            errorCalculation.updateError(this.outputLayer.getFire(), ideal[i]);
        }

        return errorCalculation.calculateRMS();
    }

    public int calculateNeuronCount() {
        int result = 0;
        for (final FeedForwardLayer layer : this.layers) {
            result += layer.getNeuronCount();
        }
        return result;
    }

    @Override
    public Object clone() {
        final FeedForwardNetwork result = cloneStructure();
        final Double copy[] = MatrixCODEC.networkToArray(this);
        MatrixCODEC.arrayToNetwork(copy, result);
        return result;
    }

    public FeedForwardNetwork cloneStructure() {
        final FeedForwardNetwork result = new FeedForwardNetwork();

        for (final FeedForwardLayer layer : this.layers) {
            final FeedForwardLayer clonedLayer = new FeedForwardLayer(layer.getNeuronCount());
            result.addLayer(clonedLayer);
        }

        return result;
    }

    public double[] computeOutputs(final double input[]) {
        if (input.length != this.inputLayer.getNeuronCount()) {
            throw new NeuralNetworkError("Size mismatch: Can't compute outputs for input size="
                    + input.length + " for input layer size=" + this.inputLayer.getNeuronCount());
        }

        for (final FeedForwardLayer layer : this.layers) {
            if (layer.isInput()) {
                layer.computeOutputs(input);
            } else if (layer.isHidden()) {
                layer.computeOutputs(null);
            }
        }

        return this.outputLayer.getFire();
    }

    public boolean equals(final FeedForwardNetwork other) {
        final Iterator<FeedForwardLayer> otherLayers = other.getLayers().iterator();

        for (final FeedForwardLayer layer : this.getLayers()) {
            final FeedForwardLayer otherLayer = otherLayers.next();

            if (layer.getNeuronCount() != otherLayer.getNeuronCount()) {
                return false;
            }

            // make sure they either both have or do not have
            // a weight matrix.
            if ((layer.getMatrix() == null) && (otherLayer.getMatrix() != null)) {
                return false;
            }

            if ((layer.getMatrix() != null) && (otherLayer.getMatrix() == null)) {
                return false;
            }

            // if they both have a matrix, then compare the matrices
            if ((layer.getMatrix() != null) && (otherLayer.getMatrix() != null)) {
                if (!layer.getMatrix().equals(otherLayer.getMatrix())) {
                    return false;
                }
            }
        }

        return true;
    }
    
    public int getHiddenLayerCount(){
        return this.layers.size() - 2;
    }
    
    public Collection<FeedForwardLayer> getHiddenLayers(){
        final Collection<FeedForwardLayer> result = new ArrayList<FeedForwardLayer>();
        
        for(final FeedForwardLayer layer : this.layers){
            if(layer.isHidden())
                result.add(layer);
        }
        
        return result;
    }
    
    public FeedForwardLayer getInputLayer(){
        return this.inputLayer;
    }
    
    public List<FeedForwardLayer> getLayers(){
        return this.layers;
    }
    
    public FeedForwardLayer getOutputLayer(){
        return this.outputLayer;
    }
    
    public int getWeightMatrixSize(){
        int result = 0;
        for (final FeedForwardLayer layer : this.layers)
            result += layer.getMatrixSize();
        
        return result;
    }
    
    public void reset(){
        for (final FeedForwardLayer layer : layers) {
            layer.reset();
        }
    }
}
